package jpave.query_interface;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Stack;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.DefaultTableCellRenderer;


import assem.Utils;

import util.Debug.ErrorReport;
import util.ui.ButtonComboBox;
import util.ui.DisplayFloat;
import util.ui.UserPrompt;

import jpave.annotator.CoreMain;
import jpave.dataholders.BasicQueryData;
import jpave.dataholders.SessionData;
import jpave.manager.ManagerFrame;

public class BasicGOQueryTab extends Tab {
	private static final String [] RESULT_COLUMNS_FLAT_GROUP = { "Go Num", "Level", "Description", "# Seq" };
	private static final Class<?> [] RESULT_TYPES_FLAT_GROUP = { Long.class, Integer.class, String.class, Integer.class }; 
	private static final String [] DB_FIELD_COLUMNS_FLAT_GROUP = { "go_info.gonum", "go_info.level", "go_info.descr", "go_info.nUnitranHit" };
	
	public static final Color DETRIMCOLOR = new Color(229, 245, 237);
	
	private static final long serialVersionUID = 7958512938141853722L;
	private static final int LABEL_WIDTH = 100;
	private static final int MIN_LEVEL = 1;
	private static final int MAX_LEVEL = 16;
	private static final Color BGCOLOR = ManagerFrame.BGCOLOR;
	private static final Color HLTCOLOR = new Color(179,195,221);

	public BasicGOQueryTab(JPaveFrame parentFrame, BasicQueryData data) {
		super(parentFrame, null);
		setBackground(Color.white);
		theParentFrame = parentFrame;
		theData = data;
		theSession = theParentFrame.getAssemblyList();
		theResults = new Vector<Object []> ();
		nLevelMin = MIN_LEVEL;
		nLevelMax = MAX_LEVEL;
		loadInitData();
		setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
		createMainPanel();
				
		pnlColumnSelect.setVisible(false);
		add(pnlColumnSelect);
		
		currentIDs = new HashSet<Long>(); 			// set of IDs obtained from the user's query settings
		treeFilterIn = new HashSet<Long>(); 		// IDs flagged by tree filter
		rowMap = new HashMap<Integer,Integer>();	// if treeFilter=true, then non-flagged results are hidden (but retained in theResults).
													// rowMap maps the row number to the actual entry in theResults.
		
		add(pnlMain);
	}
	
	private void loadInitData() {
		try {
			theDENames = new Vector<String> ();
			
			Connection conn = theSession.getConnection();
			Statement stmt = conn.createStatement(
					ResultSet.TYPE_FORWARD_ONLY,
					ResultSet.CONCUR_READ_ONLY);
			// supposed to prevent memory hogging on large query
			stmt.setFetchSize(Integer.MIN_VALUE); 
			ResultSet rset = stmt.executeQuery("SHOW COLUMNS FROM go_info LIKE 'P\\_%'");
			while(rset.next()) {
				String deName = rset.getString(1).substring(2);
				theDENames.add(deName);
			}
			rset.close();
			
			rset = stmt.executeQuery("SHOW COLUMNS FROM go_info LIKE 'term_type'");
			if(rset.next()) {
				String val = rset.getString(2);
				val = val.substring(val.indexOf('(') + 1, val.length() - 1);
				String [] valArr = val.split(",");
				theTermTypes = new String[valArr.length];
				for(int x=0; x<valArr.length; x++) {
					theTermTypes[x] = valArr[x].substring(1, valArr[x].length()-1);
				}
			}
			rset.close();
			hasSlims = false;

			selectedColumns = new boolean[RESULT_COLUMNS_FLAT_GROUP.length + theDENames.size()];
			for(int x=0; x<selectedColumns.length; x++) {
				selectedColumns[x] = true;
			}
		}
		catch(Exception e) {
			ErrorReport.prtReport(e, "Error getting library names");
		}
	}
	
	private void createMainPanel() {
		createButtonPanel();
		createControlPanel();
		createTablePanel();
		createColumnSelectPanel();
		createTableButtonPanel();
		
		pnlMain = new JPanel();
		pnlMain.setLayout(new BoxLayout(pnlMain, BoxLayout.PAGE_AXIS));
		pnlMain.setBackground(BGCOLOR);
		
		pnlMain.add(pnlButton);
		pnlMain.add(Box.createVerticalStrut(15));
		pnlMain.add(pnlControl);
		pnlMain.add(Box.createVerticalStrut(15));
		pnlMain.add(pnlTable);
		pnlMain.add(Box.createVerticalStrut(15));
		pnlMain.add(pnlTableButton);
		pnlMain.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
	}
	
	private void createTableButtonPanel() {
		lblHeader = new JLabel("Modify");
		Dimension dim = lblHeader.getPreferredSize();
		dim.width = LABEL_WIDTH;
		lblHeader.setPreferredSize(dim);
		lblHeader.setMaximumSize(lblHeader.getPreferredSize());

		btnDeleteAll = new JButton("Delete all");
		btnDeleteAll.setMargin(new Insets(0, 0, 0, 0));
		btnDeleteAll.setFont(new Font(btnDeleteAll.getFont().getName(),Font.PLAIN,10));
		btnDeleteAll.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				clearResults();
				updateTopButtons();
				txtStatus.setText("");
			}
		});
		
		btnUnselectAll = new JButton("Unselect");
		btnUnselectAll.setMargin(new Insets(0, 0, 0, 0));
		btnUnselectAll.setFont(new Font(btnUnselectAll.getFont().getName(),Font.PLAIN,10));
		btnUnselectAll.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				theTable.clearSelection();
				updateTopButtons();
			}
		});

		btnDelete = new JButton("Delete selected");
		btnDelete.setMargin(new Insets(0, 0, 0, 0));
		btnDelete.setFont(new Font(btnDelete.getFont().getName(),Font.PLAIN,10));
		btnDelete.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int [] sels = theTable.getSelectedRows();
				if(sels != null && sels.length>0) {
					for(int x=sels.length-1; x>=0; x--) {
						theResults.remove(sels[x]);
					}
					updateCurrentIDs();
					applyDETreeFilter();
					buildRowMap();
					theTableModel.fireTableDataChanged();
					txtStatus.setText("");
				}
				updateTopButtons();
			}
		});
		
		btnKeep = new JButton("Keep selected");
		btnKeep.setMargin(new Insets(0, 0, 0, 0));
		btnKeep.setFont(new Font(btnKeep.getFont().getName(),Font.PLAIN,10));
		btnKeep.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				txtStatus.setText("");
				if(theTable.getSelectedRowCount() == 0)
					clearResults();
				else {
					int [] sels = theTable.getSelectedRows();
					int selPos = sels.length - 1;
					int resultIndex = theResults.size() - 1;
					while(resultIndex >= 0) {
						if(selPos < 0 || sels[selPos] < resultIndex) {
							theResults.remove(resultIndex);
						}
						else {
							if(selPos >=0 && sels[selPos] == resultIndex)
								selPos--;
						}
						resultIndex--;
					}
					updateCurrentIDs();
					applyDETreeFilter();
				}
				buildRowMap();
				theTableModel.fireTableDataChanged();
				updateTopButtons();
				updateCurrentIDs();
			}
		});

		pnlTableButton = createPanel(true);
		pnlTableButton.add(lblHeader);
		pnlTableButton.add(btnDeleteAll);
		pnlTableButton.add(Box.createHorizontalStrut(5));
		pnlTableButton.add(btnUnselectAll);
		pnlTableButton.add(Box.createHorizontalStrut(10));
		pnlTableButton.add(btnDelete);
		pnlTableButton.add(Box.createHorizontalStrut(5));
		pnlTableButton.add(btnKeep);
		pnlTableButton.add(Box.createHorizontalGlue());
		pnlTableButton.setMaximumSize(pnlTableButton.getPreferredSize());
	}
	private void updateCurrentIDs()
	{
		currentIDs.clear();
		for (Object[] res : theResults)
		{
			long id = (Long)res[0];
			currentIDs.add(id);
		}
	}
	private void createButtonPanel() {
		pnlButton = createPanel(true);
		
		btnViewUnitrans = new JButton("View sequences for selected GO");
		btnViewUnitrans.setBackground(ManagerFrame.FUNCTIONCOLOR);
		btnViewUnitrans.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				viewContigsInTable();
			}
		});
		btnViewUnitrans.setEnabled(false);
		btnExport = new JButton("Export");
		btnExport.setBackground(ManagerFrame.PROMPTCOLOR);
		btnExport.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				saveTextToFile(theTableModel.getCSVExport());
			}
		});
		btnExport.setEnabled(false);
		btnCopyTable = new JButton("Copy Table");
		btnCopyTable.setBackground(Color.WHITE);
		btnCopyTable.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				try {
						Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
						String ts = createTableString("\t");
						cb.setContents(new StringSelection(ts), null);
					} catch (Exception e) {							
					ErrorReport.reportError(e, "Error copying table");
				} catch (Error e) {
					ErrorReport.reportFatalError(e, "Fatal error copying table", null);
				}
			}			
		});
		btnCopyTable.setEnabled(false);
		
		btnHelp = new JButton("Help");
		btnHelp.setBackground(ManagerFrame.HELPCOLOR);
		btnHelp.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				UserPrompt.displayHTMLResourceHelp(getParentFrame(), "Basic Go Query", "html/viewSingleTCW/BasicQueryGO.html");
			}
		});

		pnlButton.add(btnViewUnitrans);
		pnlButton.add(Box.createHorizontalStrut(10));
		pnlButton.add(btnExport);
		pnlButton.add(Box.createHorizontalStrut(10));
		pnlButton.add(btnCopyTable);
		pnlButton.add(Box.createHorizontalStrut(10));
		pnlButton.add(btnHelp);
		pnlButton.setMaximumSize(pnlButton.getPreferredSize());
		pnlButton.setMinimumSize(pnlButton.getPreferredSize());
	}
	
    public String createTableString(String delim) {
    		StringBuilder retVal = new StringBuilder();
    	
		for(int x=0; x<theTableModel.getColumnCount()-1; x++) {
			retVal.append(theTableModel.getColumnName(x));
			retVal.append(delim);
		}	
		retVal.append(theTableModel.getColumnName(theTableModel.getColumnCount()-1));
		retVal.append("\n");
		
		for(int x=0; x<theTableModel.getRowCount(); x++) {
			StringBuilder row = new StringBuilder();
			for(int y=0; y<theTableModel.getColumnCount()-1; y++) {
				row.append(theTableModel.getValueAt(x, y));
				row.append(delim);
			}
			retVal.append(row.toString());
			retVal.append(theTableModel.getValueAt(x, theTableModel.getColumnCount()-1));
			retVal.append("\n");
		}
    		return retVal.toString();
    }
    
    private void createColumnSelectPanel() {
	    	pnlColumnSelect = createPanel(false);
	    	
	    	JPanel selectPanel = createPanel(true);
	    	selectPanel.setBackground(BGCOLOR);
	
	    	JPanel generalColsPanel = createPanel(false);
	    	generalColsPanel.setBackground(BGCOLOR);
	
	    	generalColsPanel.setAlignmentY(Component.TOP_ALIGNMENT);
	    	
	    	generalColsPanel.add(new JLabel("General"));
	    	generalColsPanel.add(Box.createVerticalStrut(10));
	    	
	    	chkGeneralColumns = new JCheckBox[RESULT_COLUMNS_FLAT_GROUP.length];
	    	for(int x=0; x < RESULT_COLUMNS_FLAT_GROUP.length; x++) {
	    		chkGeneralColumns[x] = new JCheckBox(RESULT_COLUMNS_FLAT_GROUP[x]);
	    		chkGeneralColumns[x].setBackground(BGCOLOR);
	    		chkGeneralColumns[x].setSelected(true);
	    		generalColsPanel.add(chkGeneralColumns[x]);
	    	}
	    	
	    	JPanel libraryColsPanel = createPanel(true);
	    	libraryColsPanel.setBackground(BGCOLOR);
	    	libraryColsPanel.setAlignmentY(Component.TOP_ALIGNMENT);
	    	
	    	if (theDENames.size() > 0)
	    	{
	    		JPanel sub1 = createPanel(false);
	    		sub1.setBackground(BGCOLOR);
	    		sub1.setAlignmentY(Component.TOP_ALIGNMENT);
		    	sub1.add(new JLabel("GO DE"));
		    	sub1.add(Box.createVerticalStrut(10));
		    	chkDEColumns = new JCheckBox[theDENames.size()];
		    	for(int x=0; x<chkDEColumns.length; x++) {
		    		chkDEColumns[x] = new JCheckBox(theDENames.get(x));
		    		chkDEColumns[x].setBackground(BGCOLOR);
		    		chkDEColumns[x].setSelected(true);
		    		chkDEColumns[x].addActionListener(new ActionListener() {
						public void actionPerformed(ActionEvent e) {
							DEClicked();
						}
					});
		    		sub1.add(chkDEColumns[x]);
		    	}
		    JPanel selectionPanel = createPanel(false);
		    	selectionPanel.setBackground(BGCOLOR);
	
		    	chkSelectAllDEs = new JCheckBox("check/uncheck All");
		    	chkSelectAllDEs.setSelected(true);
		    	chkSelectAllDEs.setBackground(BGCOLOR);
		    	chkSelectAllDEs.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent arg0) {
						for(int x=0; x<chkDEColumns.length; x++)
							chkDEColumns[x].setSelected(chkSelectAllDEs.isSelected());
					}
				});
		    	
		    	selectionPanel.add(chkSelectAllDEs);
		    	
		    	selectionPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
		    	sub1.add(selectionPanel);
		    	libraryColsPanel.add(sub1);
	    	}
    	   	    	
	    	selectPanel.add(generalColsPanel);
	    	selectPanel.add(Box.createHorizontalStrut(20));
	    	selectPanel.add(libraryColsPanel);
	    	selectPanel.setMaximumSize(selectPanel.getPreferredSize());
	    	selectPanel.setMinimumSize(selectPanel.getPreferredSize());
	    	selectPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
	    	
	    	JPanel buttonPanel = createPanel(true);
	    	buttonPanel.setBackground(BGCOLOR);
	    	JButton keepButton = new JButton("Accept");
	    	keepButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (theDENames.size() > 0)
				{
					String thresh = txtDEVal.getText().trim();
					if (thresh.startsWith("e") || thresh.startsWith("E"))
					{
						thresh = "1" + thresh;
					}
					try 
					{
						Float.parseFloat(thresh);
					}
					catch(Exception e1)
					{
						System.err.println("Invalid DE threshold:" + thresh);
						return;
					}
				}
				loadColumnsFromUI();
				autofitColumns();
				pnlColumnSelect.setVisible(false);
				pnlMain.setVisible(true);
			}
		});
    	
    		JButton discardButton = new JButton("Discard");
    		discardButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				pnlColumnSelect.setVisible(false);
				pnlMain.setVisible(true);
			}
		});
	    	buttonPanel.add(keepButton);
	    	buttonPanel.add(Box.createHorizontalStrut(10));
	    	buttonPanel.add(discardButton);
	    	buttonPanel.setMaximumSize(buttonPanel.getPreferredSize());
	    	buttonPanel.setMinimumSize(buttonPanel.getPreferredSize());
	    	buttonPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
	    	
	    	JLabel tmpLabel = new JLabel("<HTML><H2>Select columns to view</H2></HTML>");
	    	tmpLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
	    	tmpLabel.setMaximumSize(tmpLabel.getPreferredSize());
	    	tmpLabel.setMinimumSize(tmpLabel.getPreferredSize());
	    	pnlColumnSelect.add(tmpLabel);
	    	pnlColumnSelect.add(Box.createVerticalStrut(10));
	    	pnlColumnSelect.add(selectPanel);
	    	pnlColumnSelect.add(Box.createVerticalStrut(25));
	    	pnlColumnSelect.add(buttonPanel);
    }
    private void enableDESection(boolean enable)
    {
	    	chkDEAll.setEnabled(enable);
	    	chkDEAny.setEnabled(enable);
	    	txtDEVal.setEnabled(enable);
	    	lblDE1.setEnabled(enable);
	    	lblDE2.setEnabled(enable);
	    	lblDE3.setEnabled(enable);
    }
    private void loadColumnsFromUI() {
		for(int x=0; x<chkGeneralColumns.length; x++) {
			selectedColumns[x] = chkGeneralColumns[x].isSelected();
		}

		for(int x=0; x<theDENames.size(); x++) {
			selectedColumns[chkGeneralColumns.length + x] = chkDEColumns[x].isSelected();
		}
		
		theTableModel.fireTableStructureChanged();
    }
    
    private void DEClicked() {
	    	if (theDENames.size() == 0) return;
	    	boolean allSelected = true;
	    	for(int x=0; x<chkDEColumns.length && allSelected; x++)
	    		allSelected = chkDEColumns[x].isSelected();
	    	chkSelectAllDEs.setSelected(allSelected);
    }

	private void createControlPanel() {
		pnlControl = createPanel(false);
				
		btnSelectColumns = new JButton("Select Columns");
		btnSelectColumns.setBackground(ManagerFrame.MENUCOLOR);
		btnSelectColumns.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				for(int x=0; x<chkGeneralColumns.length; x++)
					chkGeneralColumns[x].setSelected(selectedColumns[x]);
				
				for(int x=0; x<theDENames.size(); x++) {
					chkDEColumns[x].setSelected(selectedColumns[x + chkGeneralColumns.length]);
				}

				//Updates "Select All" button
				DEClicked();
				
				pnlColumnSelect.setVisible(true);
				pnlMain.setVisible(false);
			}
		});
		JPanel row = null;
		JLabel tmpLabel = null;

		ButtonGroup grp = new ButtonGroup();
		chkGOID = new JRadioButton("GO ID (Exact)"); // CAS 12/21/14 added GO for clarity
		chkGODescription = new JRadioButton("GO Description (Substring)");
		
		txtSubString = new JTextField(15);
		txtSubString.setMaximumSize(txtSubString.getPreferredSize());
		txtSubString.setMinimumSize(txtSubString.getPreferredSize());
		
		grp.add(chkGOID);
		grp.add(chkGODescription);
		
		tmpLabel = new JLabel("Search");
		chkGOID.setSelected(true);
		row = createPanel(true);
		JPanel modePanel = createPanel(true);
		modePanel.add(chkGOID);
		modePanel.add(Box.createHorizontalStrut(5));
		modePanel.add(chkGODescription);
		
		row.add(tmpLabel);
		row.add(Box.createHorizontalStrut(LABEL_WIDTH-tmpLabel.getPreferredSize().width));
		row.add(modePanel);
		row.add(Box.createHorizontalStrut(10));
		row.add(txtSubString);
		
		pnlControl.add(row);
		pnlControl.add(Box.createVerticalStrut(5));
		
		cmbTermTypes = new ButtonComboBox();
		cmbTermTypes.addItem("All");
		for(int x=0; x<theTermTypes.length; x++)
			cmbTermTypes.addItem(theTermTypes[x]);
		cmbTermTypes.setSelectedIndex(0);
		cmbTermTypes.setMaximumSize(cmbTermTypes.getPreferredSize());
		cmbTermTypes.setMinimumSize(cmbTermTypes.getPreferredSize());

		ButtonGroup grpLevel = new ButtonGroup();
		
		cmbSpecificLevel = new ButtonComboBox();
		for(int x=MIN_LEVEL; x<=MAX_LEVEL; x++)
			cmbSpecificLevel.addItem("" + x);
		cmbSpecificLevel.setMaximumSize(cmbSpecificLevel.getPreferredSize());
		cmbSpecificLevel.setMinimumSize(cmbSpecificLevel.getPreferredSize());
		cmbSpecificLevel.setSelectedIndex(1); 
		chkLevelSpecific = new JRadioButton("Specific");
		grpLevel.add(chkLevelSpecific);
		chkLevelRange = new JRadioButton("Range");
		grpLevel.add(chkLevelRange);
		chkLevelRange.setSelected(true);
		
		txtLevelMin = new JTextField(5);
		txtLevelMin.setMaximumSize(txtLevelMin.getPreferredSize());
		txtLevelMin.setMinimumSize(txtLevelMin.getPreferredSize());
		txtLevelMin.setText(MIN_LEVEL + "");
		
		txtLevelMax = new JTextField(5);
		txtLevelMax.setMaximumSize(txtLevelMax.getPreferredSize());
		txtLevelMax.setMinimumSize(txtLevelMax.getPreferredSize());
		txtLevelMax.setText(MAX_LEVEL + "");
		
		row = createPanel(true);
		tmpLabel = new JLabel("Type");
		row.add(tmpLabel);
		row.add(Box.createHorizontalStrut(LABEL_WIDTH - tmpLabel.getPreferredSize().width));
		row.add(cmbTermTypes);
		
		pnlControl.add(row);
		pnlControl.add(Box.createVerticalStrut(5));
		    	
		row = createPanel(true);
		tmpLabel = new JLabel("Level");
		row.add(tmpLabel);
		row.add(Box.createHorizontalStrut(LABEL_WIDTH - tmpLabel.getPreferredSize().width));
		
		JPanel innerRow = createPanel(true);
		innerRow.add(chkLevelSpecific);
		innerRow.add(Box.createHorizontalStrut(10));
		innerRow.add(cmbSpecificLevel);
		innerRow.add(Box.createHorizontalStrut(5));
		
		row.add(innerRow);
		row.add(Box.createHorizontalStrut(20));
		
		innerRow = createPanel(true);
		innerRow.add(chkLevelRange);
		innerRow.add(Box.createHorizontalStrut(10));
		
		innerRow.add(txtLevelMin);
		innerRow.add(Box.createHorizontalStrut(5));
		innerRow.add(new JLabel("to"));
		innerRow.add(Box.createHorizontalStrut(5));
		innerRow.add(txtLevelMax);
		innerRow.add(Box.createHorizontalStrut(5));
	
		row.add(innerRow);
		pnlControl.add(row);
		pnlControl.add(Box.createVerticalStrut(5));

	    	if (theDENames.size() > 0)
	    	{
	        	ButtonGroup grpDE = new ButtonGroup();
	        	chkDEAny = new JRadioButton(); grpDE.add(chkDEAny);
	        	chkDEAll = new JRadioButton(); grpDE.add(chkDEAll);
	        	chkDEAll.setSelected(true);
	        	txtDEVal = new JTextField(" 0.05",4);
	        	txtDEVal.setMaximumSize(txtDEVal.getPreferredSize());
	        	
	        	chkDETreeFilter = new JCheckBox();
	        	chkDETreeFilter.setSelected(false);
	        	chkDETreeFilter.addActionListener(new ActionListener() {
	    			public void actionPerformed(ActionEvent e) {
	    				toggleFilterView();
	    			}
	    		});   	
	    		chkUseDE = new JCheckBox();
	    		chkUseDE.setSelected(false);
	        	chkUseDE.addActionListener(new ActionListener() {
	    			public void actionPerformed(ActionEvent e) {
	    				enableDESection(chkUseDE.isSelected());
	    			}
	    		});   
	        	
		    	row = createPanel(true);
		    	tmpLabel = new JLabel("DE");
	    		row.add(tmpLabel);
	    		row.add(Box.createHorizontalStrut(LABEL_WIDTH - tmpLabel.getPreferredSize().width));
	    		
			row.setBackground(BGCOLOR);
			row.setAlignmentX(Component.LEFT_ALIGNMENT);
			row.add(chkUseDE);
			row.add(new JLabel("Threshold:  "));
		    	row.add(txtDEVal);
		    	
		    	lblDE1 = new JLabel("  Apply to DE 'Select Columns': ");
		    	lblDE2 = new JLabel(" Any ");
		    	lblDE3 = new JLabel(" All");
		    	
		    	row.add(Box.createHorizontalStrut(5));
			row.add(lblDE1);
			row.add(chkDEAny);
			row.add(lblDE2);
			row.add(chkDEAll);
			row.add(lblDE3);
							
			row.add(Box.createHorizontalGlue());
			
			enableDESection(false);

			pnlControl.add(row);
			pnlControl.add(Box.createVerticalStrut(5));
	    	}
	    	
	    	pnlControl.add(Box.createVerticalStrut(5));
		btnExecuteQuery = new JButton("ADD TO TABLE");
		btnExecuteQuery.setBackground(ManagerFrame.FUNCTIONCOLOR);
		btnExecuteQuery.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				queryFlatGroup();
			}
		});

		chkShowTree = new JCheckBox();
		chkShowTree.addActionListener(new ActionListener() 
		{
			public void actionPerformed(ActionEvent arg0) 
			{
				toggleTreeView();
			}
		});
		
		row = createPanel(true);
		tmpLabel = new JLabel("Results ");
		row.add(tmpLabel);
		row.add(Box.createHorizontalStrut(LABEL_WIDTH - tmpLabel.getPreferredSize().width));
		row.add(btnExecuteQuery);

		row.add(Box.createHorizontalStrut(20));
		row.add(btnSelectColumns);
		row.add(Box.createHorizontalStrut(20));
		row.add(chkShowTree);
		row.add(new JLabel("  Show as tree"));

		if (theDENames.size() > 0)
		{
	    		row.add(Box.createHorizontalStrut(10));
			row.add(chkDETreeFilter);
			row.add(new JLabel(" DETrim"));
		}
		
		pnlControl.add(row);
		pnlControl.add(Box.createVerticalStrut(5));
		pnlControl.add(row);

		pnlControl.setMaximumSize(pnlControl.getPreferredSize());
		pnlControl.setMinimumSize(pnlControl.getPreferredSize());
	}
	
	private void queryFlatGroup() {
		try {
			if(chkLevelRange.isSelected()) {
				nLevelMin = Integer.parseInt(txtLevelMin.getText());
				nLevelMax = Integer.parseInt(txtLevelMax.getText());
			}
			else {
				nLevelMin = nLevelMax = Integer.parseInt(cmbSpecificLevel.getSelectedItem());
			}

			Connection conn = theSession.getConnection();			
			Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
			// supposed to prevent memory hogging on large query
			stmt.setFetchSize(Integer.MIN_VALUE); 
			
			txtStatus.setText("Performing query - please wait");
			String query = getFlatGroupQuery();	
			ResultSet rset = stmt.executeQuery(query);
			ResultSetMetaData rsmd = rset.getMetaData();
			int numCols = rsmd.getColumnCount();
			
			int numRes = 0;
			while(rset.next()) {
				Object [] vals = new Object[numCols]; //RESULT_COLUMNS_FLAT_GROUP.length + theDENames.size()];
				for(int x=0; x<vals.length; x++) {
					if(x>=RESULT_COLUMNS_FLAT_GROUP.length) {
						vals[x] = rset.getDouble(x+1);
					}
					else if(RESULT_TYPES_FLAT_GROUP[x] == Long.class) {
						vals[x] = rset.getLong(x+1);
					}
					else if(RESULT_TYPES_FLAT_GROUP[x] == Integer.class) {
						vals[x] = rset.getInt(x+1);
					}
					else if(RESULT_TYPES_FLAT_GROUP[x] == String.class) {
						vals[x] = rset.getString(x+1);
					}
				}
				numRes++;
				addResult(vals,true);
			}
			if (theDENames.size() > 0)
			{
				applyDETreeFilter(); 
			}
			toggleFilterView();
			if (!treeView)	{		
				txtStatus.setText(currentIDs.size() + " GOs found; " + treeFilterIn.size() + " in trimmed set ");
			}
			autofitColumns();
			theTableModel.fireTableDataChanged();
			btnExport.setEnabled(theTable.getRowCount() > 0);
			btnCopyTable.setEnabled(theTable.getRowCount() > 0);
		}
		catch(Exception e) {
			ErrorReport.prtReport(e, "Error on GO basic flat query");
		}
	}
	private static class BorderLessTableCellRenderer extends DefaultTableCellRenderer {

	    private static final long serialVersionUID = 1L;

	    public Component getTableCellRendererComponent(
	            final JTable table,
	            final Object value,
	            final boolean isSelected,
	            final boolean hasFocus,
	            final int row,
	            final int col) {

	        final boolean showFocusedCellBorder = false; // change this to see the behavior change

	        final Component c = super.getTableCellRendererComponent(
	                table,
	                value,
	                isSelected,
	                showFocusedCellBorder && hasFocus, // shall obviously always evaluate to false in this example
	                row,
	                col
	        );
	        return c;
	    }
	}
	private void createTablePanel() {
		pnlTable = createPanel(false);
		final Color cGray = new Color(245,245,245);
		theTableModel = new GoTableModel();
		theTable = new JTable(theTableModel)
		{
		   public Component prepareRenderer(
			        TableCellRenderer renderer, int row, int column)
			    {
			        Component c = super.prepareRenderer(renderer, row, column);

			        long gonum = (Long)(theResults.get(rowMap.get(row))[0]);

			        if (treeView)
			        {
				        if (treeFilterIn.contains(gonum))
				        {
				        		c.setBackground(DETRIMCOLOR);
				        }
				        else if (currentIDs.contains(gonum))
				        {
				        		c.setBackground(Color.WHITE);
				        }
				        else
				        {
				        		c.setBackground(cGray);
				        }
			        }
			        else
			        {
			        	if (theTable.isRowSelected(row))
			        	{
			        		c.setBackground(HLTCOLOR);
			        	}
			        	else if (treeFilterIn.contains(gonum))
				        {
				        		c.setBackground(DETRIMCOLOR);
				        }	
				        else
				        {
				        		c.setBackground(Color.WHITE);
				        }
			        }
			        return c;
			    }
		};
		theTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		theTable.setCellSelectionEnabled(false);
		theTable.setRowSelectionAllowed(true);
		theTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
		theTable.setDefaultRenderer( Object.class, new BorderLessTableCellRenderer() );
		theTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent arg0) {
				updateTopButtons();
			}
		});

		txtStatus = new JTextField(400);
		txtStatus.setAlignmentX(CENTER_ALIGNMENT);
		txtStatus.setHorizontalAlignment(JTextField.CENTER);
		txtStatus.setEditable(false);
		txtStatus.setBackground(BGCOLOR);
		txtStatus.setBorder(BorderFactory.createEmptyBorder());
		Dimension dimStat = txtStatus.getPreferredSize();
		dimStat.width = 500;
		txtStatus.setPreferredSize(dimStat);
		txtStatus.setMaximumSize(txtStatus.getPreferredSize());

		JTableHeader header = theTable.getTableHeader();
		header.setUpdateTableInRealTime(true);
		colListener = theTableModel.new ColumnListener(theTable);
		header.addMouseListener(colListener);
		theTable.getTableHeader().setBackground(Color.WHITE);
		sPane = new JScrollPane(theTable);
		
		pnlTable.add(txtStatus);
		pnlTable.add(Box.createVerticalStrut(10));
		pnlTable.add(sPane);
	}
	
	private void updateTopButtons() {
		int numSels = theTable.getSelectedRowCount();
		btnViewUnitrans.setEnabled(numSels > 0);
		btnExport.setEnabled(theTable.getRowCount() > 0);
		btnCopyTable.setEnabled(theTable.getRowCount() > 0);
	}
	
	private void saveTextToFile(String lines) {
		final JFileChooser fc = new JFileChooser(theParentFrame.lastSaveFilePath);
		fc.setSelectedFile(new File("GOtable.csv"));
		final String theLines = lines;
		if (fc.showSaveDialog(theParentFrame) == JFileChooser.APPROVE_OPTION) {
			final File f = fc.getSelectedFile();
			int writeOption = JOptionPane.YES_OPTION;
			if (f.exists()) {
				writeOption = JOptionPane.showConfirmDialog(
						theParentFrame,
					    "The file already exists, overwrite it?",
					    "Save to File",
					    JOptionPane.YES_NO_OPTION);
			}
			if (writeOption == JOptionPane.YES_OPTION) {
				theParentFrame.setLastPath(f.getPath());
				Thread theThread = new Thread(new Runnable() {
					public void run() {
				    	PrintWriter pw = null;
				    	try {
				    		System.out.println("Wrote file " + f.getPath()); // CAS 12/21/14
				    		pw = new PrintWriter(new BufferedWriter(new FileWriter(f)));
				    		pw.print(theLines);
				    		pw.close();
				    	}
				    	catch(Exception e) {ErrorReport.prtReport(e, "Could not write file " + f.getPath());}
					}
				});
				theThread.start();
			}
		}
	}
	private void getAdditionalGOs(HashSet<Long> gos) 
	{
		if (gos.size() == 0) return;
		StringBuilder theQuery = new StringBuilder("SELECT " + DB_FIELD_COLUMNS_FLAT_GROUP[0]);
		
		for(int x=1; x<DB_FIELD_COLUMNS_FLAT_GROUP.length; x++) {
			if(DB_FIELD_COLUMNS_FLAT_GROUP[x].length() == 0)
				theQuery.append(", NULL");
			else 
				theQuery.append(", " + DB_FIELD_COLUMNS_FLAT_GROUP[x]);
		}
		
		for(int x=0; x<theDENames.size(); x++)
			theQuery.append(", go_info.P_" + theDENames.get(x));
		

		theQuery.append(" FROM go_info  WHERE go_info.gonum in (0");
		for (Long go : gos)
		{
			theQuery.append("," + String.valueOf(go));
		}
		theQuery.append(")");
		
		try
		{
			Connection conn = theSession.getConnection();
			
			Statement stmt = conn.createStatement(
					ResultSet.TYPE_FORWARD_ONLY,
					ResultSet.CONCUR_READ_ONLY);

			ResultSet rset = stmt.executeQuery(theQuery.toString());
			ResultSetMetaData rsmd = rset.getMetaData();
			int numCols = rsmd.getColumnCount();
			
			int numRes = 0;
			while(rset.next()) {
				Object [] vals = new Object[numCols]; //RESULT_COLUMNS_FLAT_GROUP.length + theDENames.size()];
				for(int x=0; x<vals.length; x++) {
					if(x>=RESULT_COLUMNS_FLAT_GROUP.length) {
						vals[x] = rset.getDouble(x+1);
					}
					else if(RESULT_TYPES_FLAT_GROUP[x] == Long.class) {
						vals[x] = rset.getLong(x+1);
					}
					else if(RESULT_TYPES_FLAT_GROUP[x] == Integer.class) {
						vals[x] = rset.getInt(x+1);
					}
					else if(RESULT_TYPES_FLAT_GROUP[x] == String.class) {
						vals[x] = rset.getString(x+1);
					}
				}
				numRes++;
				addResult(vals,false);
			}
			rset.close();
		}
		catch(Exception e) {ErrorReport.prtReport(e, "GO query - get additional GOs");}
	}
	private String getFlatGroupQuery() {
		String theQuery = "SELECT " + DB_FIELD_COLUMNS_FLAT_GROUP[0];
		
		for(int x=1; x<DB_FIELD_COLUMNS_FLAT_GROUP.length; x++) {
			if(DB_FIELD_COLUMNS_FLAT_GROUP[x].length() == 0)
				theQuery += ", NULL";
			else 
				theQuery += ", " + DB_FIELD_COLUMNS_FLAT_GROUP[x];
		}
		
		for(int x=0; x<theDENames.size(); x++)
			theQuery += ", go_info.P_" + theDENames.get(x);
		
		theQuery += " FROM go_info WHERE ";
		
		String str = txtSubString.getText();
		singleMode = false;
		if(str.length() > 0) {
			if(chkGOID.isSelected()) {
				String theVal = str.replaceAll("[A-Za-z:]", "");
				try {
					Integer.parseInt(theVal);
					theQuery += "go_info.gonum = " + theVal ;
					singleMode = true;
				}
				catch(Exception e) {
					System.err.println("Error: Invalid GO ID: " + str);
					theQuery += "go_info.descr LIKE '%xxyy9zzcc7bbttgg1%' AND"; // force search failure
				}
			}
			else {
				theQuery += "go_info.descr LIKE '%" + txtSubString.getText() + "%' AND";
			}
		}
		
		if (!singleMode)
		{
			if (theDENames.size() > 0 && chkUseDE.isSelected())
			{
				theQuery += buildDEClause() + " AND ";
			}
		
			if(cmbTermTypes.getSelectedIndex() > 0) {
				theQuery += "(go_info.term_type = '" + cmbTermTypes.getSelectedItem() + "') AND ";
			}
			
			if (hasSlims && chkSlim.isSelected()) {
				theQuery += " go_info.slim=1 AND ";
			}
			
			if(chkLevelSpecific.isSelected()) {
				theQuery += "go_info.level = " + cmbSpecificLevel.getSelectedItem();
			}
			else {
				theQuery += "(go_info.level >= " + nLevelMin + " AND go_info.level <= " + nLevelMax + ")";
			}
		}
		return theQuery;
	}
	private String buildDEClause()
	{
		if (theDENames.size()==0 || !chkUseDE.isSelected()) return "";
		String clause = "";
		
		String thresh = txtDEVal.getText().trim();
		if (thresh.startsWith("e") || thresh.startsWith("E")) thresh = "1" + thresh;
		
		Vector<String> DECols = new Vector<String>();
		for (int i = 0; i < theDENames.size(); i++)
		{
			JCheckBox chk = chkDEColumns[i];
			if (chk.isSelected())
			{
				DECols.add("go_info.P_" + theDENames.get(i) + "<=" + thresh);
			}
		}
		if (chkDEAny.isSelected())
		{
			clause = Utils.strVectorJoin(DECols, " or ");
		}
		else if (chkDEAll.isSelected())
		{
			clause = Utils.strVectorJoin(DECols, " and ");
		}
		
		return " (" + clause + ") ";
	}

	private void viewContigsInTable() {
		String [] contigNames = getSelectedContigs();
		
		if(contigNames.length == 1)
			getParentFrame().addContigPage(contigNames[0], this, theTable.getSelectedRow(), JPaveFrame.BasicGOQuery);
		else {
			getParentFrame().loadContigs(currentGO, contigNames, JPaveFrame.BASIC_QUERY_MODE_GO );
		}
	}
	
	public String [] getSelectedContigs() {
		int [] sels = theTable.getSelectedRows();
		int [] goIDs = new int[sels.length];
		for(int x=0; x<sels.length; x++) {
			goIDs[x] = Integer.parseInt(((String)theTableModel.getValueAt(sels[x], 0)).substring(3));
		}
		
		Vector<String> retVal = new Vector<String> ();
		try {
			Connection conn = theSession.getConnection();
			Statement stmt = conn.createStatement(
					ResultSet.TYPE_FORWARD_ONLY,
					ResultSet.CONCUR_READ_ONLY);
			// supposed to prevent memory hogging on large query
			stmt.setFetchSize(Integer.MIN_VALUE); 
			String theQuery = "SELECT contig.contigid FROM pja_unitrans_go JOIN contig ON contig.CTGID = pja_unitrans_go.CTGID WHERE pja_unitrans_go.gonum";
			if(goIDs.length == 1)
				theQuery += " = " + goIDs[0];
			else {
				theQuery += " IN (" + goIDs[0];
				for(int x=1; x<goIDs.length; x++)
					theQuery += ", " + goIDs[x];
				theQuery += ")";
			}
			currentGO = String.format("GO::%07d", goIDs[0]);
			ResultSet rset = stmt.executeQuery(theQuery);
			while(rset.next()) {
				retVal.add(rset.getString(1));
			}
		}
		catch(Exception e) {
			ErrorReport.prtReport(e, "Error getting sequence for go ID");
		}
		return retVal.toArray(new String[0]);
	}
	
	private void clearResults() {
		theResults.clear();
		rowMap.clear();
		currentIDs.clear();
		theTableModel.fireTableDataChanged();
		theTableModel.fireTableStructureChanged();
		txtStatus.setText("");
	}
	
	// The flag is false for adding GOs to complete the tree view. Those GOs aren't added to
	// the currentIDs list, which is used to restore the non-tree view if desired.
	private void addResult(Object [] newRow, boolean updateIDList) {
		long newId = (Long)newRow[0];
		
		if (!currentIDs.contains(newId)) 
		{
			theResults.add(newRow);
			if (updateIDList) 
			{
				currentIDs.add(newId);
			}
		}
	}
	private void toggleFilterView() 
	{
		
		treeFilter = (theDENames.size() > 0 ? chkDETreeFilter.isSelected() : false);
		if (treeView)
		{
			convertToTree(); // have to do it again to prune the dead branches
		}
		else
		{
			buildRowMap();
		}
		theTableModel.fireTableStructureChanged();
		theTableModel.fireTableDataChanged();
		autofitColumns();

	}
	private void buildRowMap()
	{
		rowMap.clear();
		for (int r1 = 0, r2=0; r1 < theResults.size(); r1++)
		{
			if (treeFilter)
			{
				long go = (Long)theResults.get(r1)[0];
				if (treeFilterIn.contains(go))
				{
					rowMap.put(r2, r1);
					r2++;
				}
			}
			else
			{
				rowMap.put(r1,r1);
			}	
		}		
	}
	private void toggleTreeView()
	{
		treeView = chkShowTree.isSelected();
		if (treeView)
		{
			convertToTree();
		}
		else
		{
			convertFromTree();
			buildRowMap();
		}
		theTableModel.fireTableStructureChanged();
		theTableModel.fireTableDataChanged();
		autofitColumns();
	}
	private void enableDeleteButtons(boolean enable)
	{
		btnDelete.setEnabled(enable);
		btnKeep.setEnabled(enable);
		btnDelete.setEnabled(enable);
	}
	private void convertFromTree()
	{
		Vector<Object[]> newResults = new Vector<Object[]>();		
		HashSet<Long> seen = new HashSet<Long>();
		for (Object[] r : theResults)
		{
			long go = (Long)r[0];
			if (!seen.contains(go))
			{
				if (currentIDs.contains(go))
				{
					newResults.add(r);
				}
				seen.add(go);
			}
		}
		theResults.clear();
		theResults = newResults;
		txtStatus.setText(currentIDs.size() + " GOs found; " + treeFilterIn.size() + " in trimmed set ");
		enableDeleteButtons(true);

	}
	
	// Build a tree view that includes all paths terminating in the originally-queried GOs.
	// If treeFilt=true, it also prunes out the "empty" branches having no final filtered
	// GOs on them. 
	private void convertToTree()
	{
		HashSet<Long> treeNodesSkip = new HashSet<Long>(); // IDs flagged by tree filter
		enableDeleteButtons(false);

		try
		{
			Connection conn = theSession.getConnection();			
			Statement stmt = conn.createStatement();
	
			String query = "select gonum, level+1, tblidx from pja_gotree order by tblidx asc"; 
			ResultSet rs = stmt.executeQuery(query);
			
			// Step 1: Collect the full set of GOs in the paths leading to the current set of GOs
			// Also, figure out the branches which should be pruned (i.e., don't contain a flagged GO)
			Stack<Long> curPath = new Stack<Long>();
			Stack<Long> curIdxPath = new Stack<Long>();
			Stack<Boolean> hasFiltered = new Stack<Boolean>();
			int prevLevel = 0;
			long prevGO = 0;
			long prevIdx = 0;
			boolean prevFilt = false;
			HashSet<Long> neededGOs = new HashSet<Long>();

			while (rs.next())
			{
				long go = rs.getLong(1);
				int level = rs.getInt(2);
				long tblidx = rs.getLong(3);
				
				boolean curFiltered = treeFilterIn.contains(go);

				if (level < prevLevel)
				{
					// We're jumping back up the tree, possibly more than one level.
					// As we pass through nodes that haven't been flagged to keep, flag
					// them to skip. These are nodes that don't have any flagged (green) GOs under them.
					// Likewise, the prevGO, which will always be a deepest-level leaf, i.e., having no kids.
					if (!prevFilt)
					{
						treeNodesSkip.add(prevIdx);
					}
					while(prevLevel > level)
					{
						long idx = curIdxPath.pop();
						boolean filt = hasFiltered.pop();
						if (!filt)
						{
							treeNodesSkip.add(idx);
						}
						prevLevel--;
					}
				}
				else if (level > prevLevel)
				{
					// We just went one level deeper - push the *previous* go onto the stack
					// Although note that we start by pushing 0
					curPath.push(prevGO);
					curIdxPath.push(prevIdx);
					hasFiltered.push(prevFilt);
					prevLevel = level;
				}
				else
				{
					// staying at same level, also means the previous node was a bottom node
					if (!prevFilt)
					{
						treeNodesSkip.add(prevIdx);
					}
				}
				
				// Now that the path is updated, if the current GO is filtered, 
				// label the whole path to show.
				if (curFiltered)
				{
					int pSize = hasFiltered.size();
					hasFiltered.clear();
					for (int i = 1; i <= pSize; i++)
					{
						hasFiltered.push(true);
					}
				}
				if (currentIDs.contains(go))
				{
					for (Long go2 : curPath)
					{
						if (!currentIDs.contains(go2) && go2 != 0) neededGOs.add(go2);
					}
				}
				prevGO = go;
				prevIdx = tblidx;
				prevFilt = curFiltered;
			}
			rs.close();
			
			// Step 2: get the new GOs 
			if (neededGOs.size() > 0)
			{
				getAdditionalGOs(neededGOs);
			}
			
			// Step 3: put all the GOs into tree ordering
			
			Vector<Object[]> newResults = new Vector<Object[]>();		
			// Make a helper map showing which result row it is
			HashMap<Long,Integer> go2row = new HashMap<Long,Integer>();
			int rnum = 0;
			for (Object[] r : theResults)
			{
				long go = (Long)r[0];
				go2row.put(go,rnum);
				rnum++;
			}
			
			rowMap.clear();
			query = "select gonum, level+1, tblidx from pja_gotree order by tblidx asc"; 
			rs = stmt.executeQuery(query);
			while (rs.next())
			{
				long go = rs.getLong(1);
				Integer level = rs.getInt(2);
				long idx = rs.getLong(3);
				if (go2row.containsKey(go))
				{
					Object[] oldRow = theResults.get(go2row.get(go));
					Object[] newRow = new Object[oldRow.length];
					System.arraycopy(oldRow, 0, newRow, 0, oldRow.length); // must make a copy to change level
					newRow[1] = (Object)level;
					if (treeFilter && treeNodesSkip.contains(idx))
					{
						;
					}
					else
					{
						rowMap.put(rowMap.size(), newResults.size());
					}
					newResults.add(newRow);
					nLevelMax = Math.max(nLevelMax, level);
				}
			}
			rs.close();
						
			theResults.clear();
			theResults = newResults;
			txtStatus.setText(currentIDs.size() + " GOs found; " + treeFilterIn.size() 
					+ " in trimmed set; " + rowMap.size() + " rows in tree view");
		}
		catch(Exception e){ErrorReport.prtReport(e, "Error converting to tree");}
	}

	private Double combinedGODEValue(Object[] r)
	{
		if (theDENames.size() == 0) return 1.0;
		long go = (Long)r[0];
		if (!singleMode && !currentIDs.contains(go)) return 2.0;
		
		Double ret = 1.0;
		for (int i = 0; i < theDENames.size(); i++)
		{
			JCheckBox chk = chkDEColumns[i];
			if (chk.isSelected())
			{
				int pos = RESULT_COLUMNS_FLAT_GROUP.length + i;
				Double de = (Double)r[pos];
				ret *= de;
			}
		}
		return ret;
	}
	//
	// Do the tree-aware GO DE filtering on the GOs currently in the result set,
	// using the current DE filter threshold setting.
	//
	// The method is to traverse the tree depth-first, using the pre-calculated pja_gotree, 
	// and compare each parent/child. If the child is lower, the parent is removed (note
	// that the initial theResults list should have all the results which passed the
	// query conditions, and the final trimmed set will be a subset of this). 
	// The set of siblings is tracked at each level. 
	// After a node's subtree is fully walked, i.e. when moving back up-level to that node,
	// if that node has not yet been removed, then it is better than all its child nodes,
	// hence they are removed.
	//
	// Note that GO number is stored as a long throughout (for no good reason) and int will
	// not work in the hash keys!!
	//
	private void applyDETreeFilter()
	{
		treeFilterIn.clear();
		if (theDENames.size() == 0) return;
		try
		{
			// Make some helper maps showing the DE of a go and which result row it is
			HashMap<Long,Integer> go2row = new HashMap<Long,Integer>();
			HashMap<Long,Double> go2DE = new HashMap<Long,Double>();

			int rnum = 0;
			for (Object[] r : theResults)
			{
				long go = (Long)r[0];
				Double DE = combinedGODEValue(r);
				go2row.put(go,rnum);
				go2DE.put(go, DE);
				rnum++;
			}
			
			Connection conn = theSession.getConnection();			
			Statement stmt = conn.createStatement();

			String query = "select gonum, level+1, tblidx from pja_gotree where 1 "; 
			if(cmbTermTypes.getSelectedIndex() > 0) {
				query += " and term_type = '" + cmbTermTypes.getSelectedItem() + "' ";
			}
			query += " order by tblidx asc"; 
			ResultSet rs = stmt.executeQuery(query);
			HashSet<Integer> seen = new HashSet<Integer>();
			HashSet<Integer> remove = new HashSet<Integer>();
			int prevLevel = 0;
			long prevGO = 0;
			double prevDE = 1.0;
			//double prevBestDE = 1.0;
			Stack<Long> curPath = new Stack<Long>();
			Stack<Double> curDEPath = new Stack<Double>();
			Stack<HashSet<Long>> pathSibs = new Stack<HashSet<Long>>();
			HashSet<Long> curSibs = null; // collecting siblings at each level
			while (rs.next())
			{
				long go = rs.getInt(1);
				int level = rs.getInt(2);
				int idx = rs.getInt(3);
				double DE = (go2DE.containsKey(go) ? go2DE.get(go) : 1.0);
				if (level > prevLevel+1)
				{
					CoreMain.PrtWarn("Too large level jump " + rs.getInt(4));
				}
				if (level < prevLevel)
				{
					// We're jumping back up the tree, possibly more than one level
					// This means that the current sibling set is complete, as are all those
					// which we pass through on the way up. 
					// For each sibling set, if their parent is DE and has not yet been removed, it means
					// all the kids should be removed.
					
					while(prevLevel > level)
					{
						long parentGO = curPath.peek();
						if (go2row.containsKey(parentGO)) 
						{
							int parentRow = go2row.get(parentGO);
							if (!remove.contains(parentRow))
							{
								int removed = 0;
								for (long kid : curSibs)
								{
									if (go2row.containsKey(kid))
									{
										if (!remove.contains(go2row.get(kid)))
										{
											remove.add(go2row.get(kid));
											removed++;
										}
									}
								}
							}
						}
						curPath.pop();
						curDEPath.pop();
						curSibs = pathSibs.pop();
						prevLevel--;
					}
				}
				else if (level > prevLevel)
				{
					// We just went one level deeper - push the *previous* go and its current sibling set (which
					// may not yet be complete) onto the stack
					// Although note that we start by pushing 0
					curPath.push(prevGO);
					curDEPath.push(prevDE);
					pathSibs.push(curSibs);
					
					prevLevel = level;
					curSibs = new HashSet<Long>();
				}
					
				long parentGO = curPath.peek();
				double parentDE = curDEPath.peek();
				if (go2row.containsKey(go) && go2row.containsKey(parentGO))
				{
					if (parentDE >= DE)
					{
						remove.add(go2row.get(parentGO)); // parent is not better, hence prefer child
					}
				}
				curSibs.add(go);
				prevGO = go;
				prevDE = DE;
			}
			
			treeFilterIn.clear();
			for (int i = 0; i < theResults.size(); i++)
			{
				if (!remove.contains(i))
				{
					treeFilterIn.add((Long)theResults.get(i)[0]);
				}
			}
		}
		catch(Exception e){ErrorReport.prtReport(e, "Applying DE GO filter");}		
	}
	
	static private JPanel createPanel(boolean xAxis) {
		JPanel retVal = new JPanel();
		
		retVal.setBackground(BGCOLOR);
		retVal.setAlignmentX(Component.LEFT_ALIGNMENT);
		if(xAxis)
			retVal.setLayout(new BoxLayout(retVal, BoxLayout.LINE_AXIS));
		else
			retVal.setLayout(new BoxLayout(retVal, BoxLayout.PAGE_AXIS));
		
		return retVal;
	}
	
	//Main panel, hidden when selecting columns
	private JPanel pnlMain = null;
	//Top button panel
	private JPanel pnlButton = null;
	private JButton btnViewUnitrans = null;
	private JButton btnExport = null;
	private JButton btnCopyTable = null;
	private JButton btnHelp = null;
	//Table button panel
	private JPanel pnlTableButton = null;
	private JButton btnDelete = null, btnKeep = null;
	private JButton btnDeleteAll = null, btnUnselectAll = null;
	private JLabel lblHeader = null;
	//Column select panel
	private JPanel pnlColumnSelect = null;
	private JCheckBox [] chkGeneralColumns = null;
	private JCheckBox [] chkDEColumns = null;
	private JCheckBox chkSelectAllDEs = null;
	//Control panel
	private JPanel pnlControl = null;
	private JButton btnSelectColumns = null;
	private JRadioButton chkGOID = null;
	private JRadioButton chkGODescription = null;
	private JTextField txtSubString = null;
	private ButtonComboBox cmbTermTypes = null;
	private ButtonComboBox cmbSpecificLevel = null;
	private JRadioButton chkLevelSpecific = null;
	private JRadioButton chkLevelRange = null;
	private JTextField txtLevelMin = null;
	private JTextField txtLevelMax = null;
	private JCheckBox chkSlim = null;
	private boolean hasSlims = false;
	private JTextField txtDEVal = null;
	private JRadioButton chkDEAny = null, chkDEAll = null;
	private JCheckBox chkDETreeFilter = null;
	private JCheckBox chkShowTree = null;
	private boolean treeView = false;
	private boolean treeFilter = false;
	private JLabel lblDE1=null, lblDE2=null, lblDE3=null;
	private JCheckBox chkUseDE = null;
	private JButton btnExecuteQuery = null;
	//Table panel
	private JPanel pnlTable = null;
	private GoTableModel theTableModel = null;
	private JTable theTable = null;
	private GoTableModel.ColumnListener colListener = null;
	private JScrollPane sPane = null;
	//Data members
	private int nLevelMin = -1;
	private int nLevelMax = -1;
	private JPaveFrame theParentFrame = null;
	private BasicQueryData theData = null;
	private SessionData theSession = null;
	private Vector<Object []> theResults = null;
	private Vector<String> theDENames = null;
	private boolean [] selectedColumns = null;
	private String [] theTermTypes = null;
	private String currentGO = null; 
	private HashSet<Long> currentIDs;
	private HashSet<Long> treeFilterIn = null;
	private HashMap<Integer,Integer> rowMap = null;
	boolean singleMode = false;
	private JTextField txtStatus = null;
	
    private void autofitColumns() {
        TableColumn column;
        Component comp;
        int headerWidth;
        int cellWidth;
        TableCellRenderer headerRenderer = theTable.getTableHeader().getDefaultRenderer();
        
        for (int i = 0;  i < theTable.getColumnCount();  i++) { // for each column
            column = theTable.getColumn(theTable.getColumnName(i));

            comp = headerRenderer.getTableCellRendererComponent(
            					theTable, column.getHeaderValue(),
                                 false, false, 0, i);
            
            headerWidth = comp.getPreferredSize().width + 10; // why +10 ???
            
            cellWidth = 0;
            for (int j = 0;  j < theTable.getRowCount();  j++) { // for each row
	            comp = theTable.getDefaultRenderer(theTable.getColumnClass(i)).
	            			getTableCellRendererComponent(
	                                 theTable, theTable.getValueAt(j, i),
	                                 false, false, j, i);
	            cellWidth = Math.max(cellWidth, comp.getPreferredSize().width + 10);
	            if (j > 100) break; // only check beginning rows, for performance reasons
            }

            column.setPreferredWidth(Math.max(headerWidth, cellWidth));
        }
    }

	
	private class GoTableModel extends AbstractTableModel {
		private static final long serialVersionUID = -314853582139888170L;

		public GoTableModel() {
		}
		
		// Takes the column number on the visible table, which may have some hidden columns,
		// and maps it to the column number that would be seen on the table with *all* columns showing
		private int getMappedColumn(int col) {
			int counter = -1;
			for(int x=0; x<selectedColumns.length; x++) {
				if(selectedColumns[x])
					counter++;
				if(counter == col)
					return x;
			}
			if (treeView)
			{
				// It must be a level column.
				return selectedColumns.length + (col-counter);
			}
			System.err.println("MappedColumn Error: col index " + col + ", counter=" + counter);
			return -1;
		}
		
		public Class<?> getColumnClass(int columnIndex) {
			int index = getMappedColumn(columnIndex);
			if(index < RESULT_TYPES_FLAT_GROUP.length)
				return RESULT_TYPES_FLAT_GROUP[index];
			return String.class;
		}
		
		public String getColumnName(int columnIndex) {
			int index = getMappedColumn(columnIndex);
			if(index < RESULT_COLUMNS_FLAT_GROUP.length)
				return RESULT_COLUMNS_FLAT_GROUP[index];
			if (treeView)
			{
				if (theDENames.size() > 0)
				{
					if (index == selectedColumns.length+1)
					{
						return "combDE";
					}
					else if (index > selectedColumns.length+1)
					{
						int levelNum = nLevelMin + (index - selectedColumns.length -2);
						return String.valueOf(levelNum);
					}
				}
				else
				{
					if (index > selectedColumns.length)
					{
						int levelNum = nLevelMin + (index - selectedColumns.length -2);
						return String.valueOf(levelNum);
					}					
				}
			}
			if (theDENames.size() > 0)
			{
				return theDENames.get(index - RESULT_COLUMNS_FLAT_GROUP.length);
			}
			else
			{
				System.err.println("Bad column:" + index + " diff:" + (index - RESULT_COLUMNS_FLAT_GROUP.length));
				return "NA";
			}
		}
		
		public int getColumnCount() {
			int numCols = 0;
			int index = 0;
			for(; index<selectedColumns.length; index++) {
				if(selectedColumns[index])
					numCols++;
			}
			if (treeView)
			{
				numCols += (nLevelMax-nLevelMin+1) + 1;
			}
			return numCols;
		}

		public int getRowCount() {
			return rowMap.keySet().size();
		}

		public Object getValueAt(int rowx, int col) {
			int index = getMappedColumn(col);

			if (treeView)
			{
				int row = rowMap.get(rowx);
				long go = (Long)theResults.get(row)[0];
				if(index == 0)
					return String.format("GO:%07d", go);
				if (index == selectedColumns.length+1)
				{
					return new DisplayFloat(combinedGODEValue(theResults.get(row)));
				}
				else if (index > selectedColumns.length+1)
				{
					int level = (Integer)theResults.get(row)[1];
					int levelCol = nLevelMin + (index - selectedColumns.length - 2);
					if (level==levelCol)
					{
						return String.format("" + go);
					}
					else
					{
						return "";
					}
				}
				if(index >= RESULT_COLUMNS_FLAT_GROUP.length) {
					double theVal = ((((Double)theResults.get(row)[index]).doubleValue()));
					return new DisplayFloat(theVal);
				}
				return theResults.get(row)[index];
			}
			else
			{
				int row = rowMap.get(rowx);
				long go = (Long)theResults.get(row)[0];
				if(index == 0)
					return String.format("GO:%07d", go);
				if(index >= RESULT_COLUMNS_FLAT_GROUP.length) {
					double theVal = ((((Double)theResults.get(row)[index]).doubleValue()));
					return new DisplayFloat(theVal);
				}
				return theResults.get(row)[index];
			}
		}
		
		private String convertToCSV(Object val) {
			if(val == null) return "";
			if(val.getClass() == String.class) {
				if(((String)val).indexOf(',') >= 0) {
					return "\"" + ((String)val) + "\"";
				}
			}
			return val.toString();
		}

		public String getCSVExport() {
			StringBuilder retVal = new StringBuilder();

			for(int x=0; x<theTableModel.getColumnCount()-1; x++) {
				retVal.append(theTableModel.getColumnName(x));
				retVal.append(",");
			}	
			retVal.append(theTableModel.getColumnName(theTableModel.getColumnCount()-1));
			retVal.append("\n");
			
			int x, y;
			String val = "";
			for(x=0; x<getRowCount(); x++) {
				for(y=0; y<getColumnCount()-1; y++) {
					if(getValueAt(x, y) != null)
						val = getValueAt(x, y).toString();
					else
						val = "";
					//Remove spaces if header
					if(x==0)
						val = val.replaceAll("\\s", "");
					retVal.append(convertToCSV(val));
					retVal.append(",");
				}
				if(getValueAt(x, y) != null)
					val = getValueAt(x, y).toString();
				else 
					val = "";
				//Remove spaces if header
				if(x==0)
					val = val.replaceAll("\\s", "");
				
				retVal.append(convertToCSV(val));
				if(x+1 < getRowCount()) 
					retVal.append("\n");
			}
			
			return retVal.toString();
		}
		
 	  	public class ColumnListener extends MouseAdapter {
		    protected JTable table;

		    public ColumnListener(JTable t) {
		    	table = t;
		    	bAscend = new boolean[table.getColumnCount()];
		    	for(int x=0; x<bAscend.length; x++)
		    		bAscend[x] = true;
		    }

		    public void mouseClicked(MouseEvent e) {
		    		sortTable(e.getX(), SwingUtilities.isLeftMouseButton(e));
		    		theTableModel.fireTableDataChanged();
		    }
  	  	
  	  		private void sortTable(int xLocation, boolean leftClick) {
  	  			TableColumnModel colModel = table.getColumnModel();
  	  			int columnModelIndex = (colModel.getColumnIndexAtX(xLocation));
  	  			int modelIndex = (colModel.getColumn(columnModelIndex).getModelIndex());
	    	
  	  			if (modelIndex < 0)
  	  				return;

  	  			if(leftClick)
   	  				bAscend[modelIndex] = !bAscend[modelIndex];
  	  			else
  	  				bAscend[modelIndex] = true;
  	  			
   	  			sort(modelIndex, bAscend[modelIndex]);   	  			
  	  		}
  	  		
  	  		private void sort(final int sortColumn, final boolean ascend) {
  	  			Collections.sort(theResults, new Comparator<Object []>() {

					public int compare(Object[] arg0, Object[] arg1) {
						int retMask = 1;
						if(!ascend) retMask = -1;
		  	  			Class<?> theType = table.getModel().getColumnClass(sortColumn);
		  	  			int mappedColumn = getMappedColumn(sortColumn);
		  	  			
		  				if(mappedColumn >= RESULT_COLUMNS_FLAT_GROUP.length) {
		  	  				return retMask * ((Double)arg0[mappedColumn]).compareTo((Double)arg1[mappedColumn]);
		  				}
		  	  			if(theType == String.class)
		  	  				return retMask * ((String)arg0[mappedColumn]).compareTo((String)arg1[mappedColumn]);
		  	  			if(theType == Integer.class)
		  	  				return retMask * ((Integer)arg0[mappedColumn]).compareTo((Integer)arg1[mappedColumn]);
		  	  			if(theType == Double.class)
		  	  				return retMask * ((Double)arg0[mappedColumn]).compareTo((Double)arg1[mappedColumn]);
		  	  			if(theType == Long.class)
		  	  				return retMask * ((Long)arg0[mappedColumn]).compareTo((Long)arg1[mappedColumn]);
						return 0;
					}
  	  				
  	  			});
  	  		}
  	  	}

 	  	private boolean [] bAscend = null;
	}
}
